﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Main.PrimitivesCommunication;
using System.IO.Pipes;
using System.Threading;
using Core.Connexions;

namespace Main.Transport
{
    class CoucheTransport
    {
        public NamedPipeServerStream pipeTransportToReseau;
        public NamedPipeClientStream pipeReseauToTransport;

        /// <summary>
        /// Méthode s'occupant d'envoyer les réponses de session à la couche réseau sous 
        /// forme de primitives
        /// </summary>
        public void EcrireVersReseau()
        {
            pipeTransportToReseau = new NamedPipeServerStream(REGLES.PIPE_TRANSPORT_TO_RESEAU);
            StreamWriter streamWriterTransportToReseau = new StreamWriter(pipeTransportToReseau);

            Console.WriteLine("Attente de connexion sur le pipe TRANSPORT_TO_RESEAU");
            pipeTransportToReseau.WaitForConnection();
            streamWriterTransportToReseau.AutoFlush = true;

            byte applicationId;
            byte addresseDestination;
            string data;
            string[] argsSession;
            string transactionSession = String.Empty;
            ConnexionTransport ConnexionTransport;

            //lecture du fichier session
            Console.WriteLine("Debut : Lecture de S_LECTURE");
            using (StreamReader sr = new StreamReader(REGLES.S_LECTURE))
            {
                while ((transactionSession = sr.ReadLine()) != null)
                {
                    Console.WriteLine("\tLigne lue dans S_LECTURE : " + transactionSession);

                    argsSession = transactionSession.Split(';');
                    applicationId = Convert.ToByte(argsSession[0]);
                    addresseDestination = Convert.ToByte(argsSession[1]);
                    data = transactionSession;
                    byte adresseSource = applicationId;
                    byte adresseDestination;

                    adresseSource = applicationId;

                    do
                    {
                        adresseDestination = GenererAdresse();
                    } while (ConnexionReseau.ListeConnexions.Any(c => c.AdresseDestination == adresseDestination));

                    ConnexionTransport = ConnexionTransport.GetConnexion(applicationId, adresseSource, addresseDestination);
                    ConnexionTransport.Data.Enqueue(data);

                    if (ConnexionTransport.Etat == EtatsConnexion.AttenteConnexion)
                    {
                        //Envoi de la primitive vers réseau
                        Primitive connexionReq = Primitive.CreateNConnectReq(ConnexionTransport.IdConnexionLogique, ConnexionTransport.AdresseSource, ConnexionTransport.AdresseDestination);
                        
                        Console.WriteLine("\tEnvoi vers Reseau : " + connexionReq.ToString());
                        connexionReq.Send(streamWriterTransportToReseau);
                        
                        ConnexionTransport.Etat = EtatsConnexion.AttenteConfirmation;
                    }

                    TraiterReponse(streamWriterTransportToReseau);
                }
            }
            Console.WriteLine("Fin : Lecture de S_LECTURE");

            //Tant qu'il reste des connexions
            while (ConnexionTransport.ListeConnexions.Count > 0)
            {
                TraiterReponse(streamWriterTransportToReseau);
                Thread.Sleep(50);
            }
        }

        /// <summary>
        /// Méthode s'occupant de traiter les réponses
        /// </summary>
        public void TraiterReponse(StreamWriter sw)
        {
            List<ConnexionTransport> listeConnexions = ConnexionTransport.ListeConnexions.Where(c => c.Etat == EtatsConnexion.ConnexionEtablie).ToList();
            Primitive primitiveAEnvoyer;
            string lineIdConnexion = String.Empty;
            
            foreach (ConnexionTransport connexionTransport in listeConnexions)
            {
                //Tant qu'il y a des données à envoyer on les envois
                while(connexionTransport.Data.Count > 0)
                {
                    primitiveAEnvoyer = Primitive.CreateNDataReq(connexionTransport.IdConnexionLogique, connexionTransport.Data.Dequeue());
                    Console.WriteLine("Traitement de la connexion #" + connexionTransport.IdConnexionLogique+"\n\tEnvoi de donnees : " + primitiveAEnvoyer.ToString());
                    primitiveAEnvoyer.Send(sw);
                }

                //Lorsqu'il n'y a plus rien à envoyer on envoi une demande de déconnexion
                primitiveAEnvoyer = Primitive.CreateNDisconnectReq(connexionTransport.IdConnexionLogique);
                connexionTransport.Etat = EtatsConnexion.AttenteConfirmation;

                Console.WriteLine("\tFin des donnees : " + primitiveAEnvoyer.ToString());
                primitiveAEnvoyer.Send(sw);
            }
        }

        /// <summary>
        /// Méthode exécutée par un thread s'occupant de lire les primitives venant de la couche réseau
        /// </summary>
        public void LireDeReseau()
        {
            pipeReseauToTransport = new NamedPipeClientStream(REGLES.PIPE_RESEAU_TO_TRANSPORT);
            StreamReader streamReaderReseauToTransport = new StreamReader(pipeReseauToTransport);
            pipeReseauToTransport.Connect();
            Console.WriteLine("Lire de Reseau : connecte au pipe RESEAU_TO_TRANSPORT"); 

            ConnexionTransport connection;
            Primitive primitiveRecu = null;
            string ligneReseau;
            
            while (true)
            {
                ligneReseau = streamReaderReseauToTransport.ReadLine();

                if (ligneReseau != null)
                {
                    primitiveRecu = Primitive.Receive(ligneReseau);
                    Console.WriteLine("Primitive recue : " + primitiveRecu.ToString());

                    connection = ConnexionTransport.GetConnexion(primitiveRecu.AdresseReponse);

                    if (connection != null)
                    {
                        //on traite les primitives reçues
                        switch (primitiveRecu.PrimitiveType)
                        {
                            case PrimitiveTypes.NConnectConfirmation:
                                connection.Etat = EtatsConnexion.ConnexionEtablie;
                                break;

                            case PrimitiveTypes.NDisconnectIndication:
                                connection.Destroy();
                                break;
                        }
                    }

                    //Écriture des réponses envoyé
                    using (StreamWriter sw = new StreamWriter(REGLES.S_ECRITURE, true))
                    {
                        sw.WriteLine(primitiveRecu.ToString());
                    }
                }
            }
        }
        
        /// <summary>
        /// Méthode s'occupant de générer une addresse
        /// </summary>
        public byte GenererAdresse()
        {
            Random rand = new Random();
            return (byte)rand.Next(1, 249);
        }
    }
}
